
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A lock free hashmap that stores only 1 value per key % hashmap buckets
 * @author hussein
 */
public class HashMapCache<K, V> {

    public class Node<K, V> {
        public K key;
        public V value;

        public Node() {
            key = null;
            value = null;
        }

    }

    private ArrayList<AtomicReference<Node<K, V>>> buckets;
    private int numOfBuckets;
    private boolean hasCache;
    /*
     * Creates a HashMapCache with an initial size of numOfBuckets
     */
    public HashMapCache(int numOfBuckets, boolean hasCache) {
        this.numOfBuckets = numOfBuckets;
	this.hasCache = hasCache;
        buckets = new ArrayList<AtomicReference<Node<K, V>>>(numOfBuckets);
        for(int i = 0; i < numOfBuckets; i++) {
            AtomicReference<Node<K, V>> n = new AtomicReference<Node<K, V>>();
            n.set(new Node<K, V>());
            buckets.add(i, n);
        }
    }

    /*
     * Puts the Key Value pair in to the HashMapCache
     */
    public void put(K key, V value) {
        int bucket = Math.abs(key.hashCode() % numOfBuckets);
        Node<K, V> oldNode;

        Node<K, V> newNode = new Node<K, V>();
        newNode.key = key;
        newNode.value = value;
        do {
            oldNode = buckets.get(bucket).get();
        } while (!buckets.get(bucket).compareAndSet(oldNode, newNode));
    }

    /*
     * Returns the value mapped to the key.
     * Returns null if key doesn't exist
     */
    public V get(K key) {
        if (!hasCache) {
            return null;
        }
        V value = null;
        int bucket = Math.abs(key.hashCode() % numOfBuckets);
        Node<K, V> node = buckets.get(bucket).get();

        if (node.key != null && key.hashCode() == node.key.hashCode()) {
            value = node.value;
        }
        return value;
    }
}
